package com.hj.controller;

import com.hj.constants.LoginConstants.RepCodeEnum;
import com.hj.dto.CaptchaDto;
import com.hj.util.HttpRequestUtils;
import com.hj.util.MD5;
import com.hj.vo.LoginVO;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * @author doudh
 * @date 2020/8/25
 * @description 登录验证
 */
@RestController
public class LoginController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private static final String DEFAULT_PASSWORD = "abc123";

    /**
     * 密码登录
     *
     * @param loginVO
     * @author doudh
     * @date 2020/9/9 16:11
     */
    @PostMapping("/login/password")
    public CaptchaDto password(@RequestBody LoginVO loginVO) {
        String mobile = loginVO.getMobile();
        if (!validateMobile(mobile)) {
            return RepCodeEnum.MOBILE_ERROR.parseError();
        }

        String password = loginVO.getPassword();

        Boolean isPasswordManyTimeError = isPasswordManyTimeError(mobile);
        if (!isPasswordManyTimeError) {
            // 密码多次输错
            return RepCodeEnum.PASSWORD_MANY_ERROR.parseError();
        }
        if (!DEFAULT_PASSWORD.equals(password)) {
            if (incPasswordErrorTime(mobile)) {
                return RepCodeEnum.PASSWORD_ERROR.parseError();
            } else {
                return RepCodeEnum.PASSWORD_MANY_ERROR.parseError();
            }
        }
        return RepCodeEnum.SUCCESS.parseError();
    }

    /**
     * 短信吗登录
     *
     * @param loginVO
     * @author doudh
     * @date 2020/9/9 16:11
     */
    @PostMapping("/login/code")
    public CaptchaDto code(@RequestBody LoginVO loginVO) {
        String mobile = loginVO.getMobile();
        if (!validateMobile(mobile)) {
            return RepCodeEnum.MOBILE_ERROR.parseError();
        }

        Boolean isMsgCodeManyTimeError = isMsgCodeManyTimeError(mobile);
        if (!isMsgCodeManyTimeError) {
            return RepCodeEnum.MSGCODE_MANY_ERROR.parseError();
        }

        boolean check = checkCode(mobile, loginVO.getCode());
        if (!check) {
            if (incMsgCodeErrorTime(mobile)) {
                return RepCodeEnum.MSGCODE_ERROR.parseError();
            } else {
                return RepCodeEnum.MSGCODE_MANY_ERROR.parseError();
            }
        }
        return RepCodeEnum.SUCCESS.parseError();
    }

    /**
     * 发送短信码
     *
     * @param request
     * @param loginVO
     * @author doudh
     * @date 2020/9/9 16:11
     */
    @PostMapping("/sendCode")
    public CaptchaDto sendCode(HttpServletRequest request, @RequestBody LoginVO loginVO) {
        String mobile = loginVO.getMobile();
        if (!validateMobile(mobile)) {
            return RepCodeEnum.MOBILE_ERROR.parseError();
        }
        // 根据手机号和IP设置缓存锁（1分钟内最多允许调用1次）
        String clientIp = HttpRequestUtils.getIpAddr(request);
        String lockKey = LOGON_CODE_LOCK + MD5.str2Md5(mobile + clientIp);
        String count = stringRedisTemplate.opsForValue().get(lockKey);
        if (StringUtils.isNotBlank(count)) {
            Long expire = stringRedisTemplate.getExpire(lockKey);
            if (stringRedisTemplate.getExpire(lockKey) > 0) {
                CaptchaDto captchaDto = RepCodeEnum.SUCCESS.parseError();// 缓冲锁未过期
                captchaDto.setRepData(expire);
                return captchaDto;
            }
        }
        stringRedisTemplate.opsForValue().set(lockKey, "1", 1, TimeUnit.MINUTES);

        sendCode(mobile);
        clearMsgCodeErrorCache(mobile);

        CaptchaDto captchaDto = RepCodeEnum.SUCCESS.parseError();
        captchaDto.setRepData(60);
        return captchaDto;
    }

    /**
     * 密码登陆错误次数
     */
    private static String PASSWORD_LOGIN_ERROR_TIME_MOBILE_ = "password_login_error_time_mobile:";
    /**
     * 密码登陆错误次数过期时间(单位秒)
     */
    private static Long PASSWORD_LOGIN_ERROR_TIME_MOBILE_EXPIRE_TIME = 10 * 60L;
    /**
     * 密码登陆是否错误多次
     */
    private static String PASSWORD_LOGIN_ERROR_MANY_TIME_MOBILE_ = "password_login_error_many_time_mobile:";
    /**
     * 密码登陆是否错误多次过期时间(单位秒)
     */
    private static Long PASSWORD_LOGIN_ERROR_MANY_TIME_MOBILE_EXPIRE_TIME = 5 * 60L;

    /**
     * 验证码登陆错误次数
     */
    private static String MSGCODE_LOGIN_ERROR_TIME_MOBILE_ = "msgcode_login_error_time_mobile:";
    /**
     * 验证码登陆错误次数过期时间(单位秒)
     */
    private static Long MSGCODE_LOGIN_ERROR_TIME_MOBILE_EXPIRE_TIME = 10 * 60L;
    /**
     * 验证码登陆是否错误多次
     */
    private static String MSGCODE_LOGIN_ERROR_MANY_TIME_MOBILE_ = "msgcode_login_error_many_time_mobile:";

    /**
     * 验证码
     */
    private static String MSGCODE_LOGIN_MOBILE_ = "msgcode_login_mobile:";
    /**
     * 验证码过期时间
     */
    private static Long MSGCODE_LOGIN_MOBILE_EXPIRE_TIME = 10 * 60L;

    public static String LOGON_CODE_LOCK = "LOGON_CODE_LOCK:";

    /**
     * 手机号段验证正则
     */
    private static final String REGEX_MOBILE =
        "^((13[0-9])|(14[5,6,7,8,9])|(15([0-3]|[5-9]))|(16[5,6])|(17([0-9]))|(18[0-9])|(19([0-2]|[6-9])))\\d{8}$";
    public static final Pattern MOBILE_PATTER = Pattern.compile(REGEX_MOBILE);// 手机格式

    /**
     * 密码是否错误输入多次
     */
    private Boolean isPasswordManyTimeError(String mobile) {
        Pair<Boolean, Long> pair =
            Pair.of(stringRedisTemplate.opsForValue().get(PASSWORD_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile) != null,
                stringRedisTemplate.getExpire(PASSWORD_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile, TimeUnit.SECONDS));
        if (pair.getLeft()) {
            return false;
        }
        return true;
    }

    /**
     * 验证码是否错误输入多次
     */
    private Boolean isMsgCodeManyTimeError(String mobile) {
        Pair<Boolean, Long> pair =
            Pair.of(stringRedisTemplate.opsForValue().get(MSGCODE_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile) != null,
                stringRedisTemplate.getExpire(MSGCODE_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile, TimeUnit.SECONDS));
        if (pair.getLeft()) {
            return false;
        }
        return true;
    }

    /**
     * 记录密码错误次数及否错误输入3次 若错误输入3次返回false
     */
    private Boolean incPasswordErrorTime(String mobile) {
        String keyErrorTime = PASSWORD_LOGIN_ERROR_TIME_MOBILE_ + mobile;
        String key3ErrorTime = PASSWORD_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile;
        String count = stringRedisTemplate.opsForValue().get(keyErrorTime);
        if (count == null) {
            stringRedisTemplate.opsForValue().set(keyErrorTime, "1", PASSWORD_LOGIN_ERROR_TIME_MOBILE_EXPIRE_TIME,
                TimeUnit.SECONDS);
        } else if (Integer.valueOf(count) >= 2) {
            stringRedisTemplate.opsForValue().set(key3ErrorTime, "1", PASSWORD_LOGIN_ERROR_MANY_TIME_MOBILE_EXPIRE_TIME,
                TimeUnit.SECONDS);
            stringRedisTemplate.delete(keyErrorTime);
            return false;
        } else {
            stringRedisTemplate.opsForValue().increment(keyErrorTime, 1);
        }
        return true;
    }

    /**
     * 清理验证码输错信息，获取验证码时调用
     */
    private void clearMsgCodeErrorCache(String mobile) {
        String keyErrorTime = MSGCODE_LOGIN_ERROR_TIME_MOBILE_ + mobile;
        String key3ErrorTime = MSGCODE_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile;
        stringRedisTemplate.delete(keyErrorTime);
        stringRedisTemplate.delete(key3ErrorTime);
    }

    /**
     * 记录验证码错误次数及否错误输入3次 若错误输入3次返回false
     */
    private Boolean incMsgCodeErrorTime(String mobile) {
        String keyErrorTime = MSGCODE_LOGIN_ERROR_TIME_MOBILE_ + mobile;
        String key3ErrorTime = MSGCODE_LOGIN_ERROR_MANY_TIME_MOBILE_ + mobile;
        Long expire = 5 * 60L;;
        String count = stringRedisTemplate.opsForValue().get(keyErrorTime);
        if (count == null) {
            stringRedisTemplate.opsForValue().set(keyErrorTime, "1", MSGCODE_LOGIN_ERROR_TIME_MOBILE_EXPIRE_TIME,
                TimeUnit.SECONDS);
        } else if (Integer.valueOf(count) >= 2) {
            stringRedisTemplate.opsForValue().set(key3ErrorTime, "1", expire, TimeUnit.SECONDS);
            stringRedisTemplate.delete(keyErrorTime);
            return false;
        } else {
            stringRedisTemplate.opsForValue().increment(keyErrorTime, 1);
        }
        return true;
    }

    /**
     * @return 校验通过 true ，不通过 false
     * @description 校验手机号格式
     * @Param str 手机号
     */
    private boolean validateMobile(String str) {
        if (StringUtils.isBlank(str)) {
            return false;
        } else {
            return MOBILE_PATTER.matcher(str).matches();
        }
    }

    /**
     * @description 发送验证码
     * @Param mobile 手机号
     */
    private void sendCode(String mobile) {
        String smsCode = mobile.substring(mobile.length() - 6);
        stringRedisTemplate.opsForValue().set(MSGCODE_LOGIN_MOBILE_ + mobile, smsCode, MSGCODE_LOGIN_MOBILE_EXPIRE_TIME,
            TimeUnit.SECONDS);

    }

    /**
     * @return 校验通过 true ，不通过 false
     * @description 校验短信码
     * @Param mobile 手机号 code 短信吗
     */
    private boolean checkCode(String mobile, String code) {
        String cacheCode = stringRedisTemplate.opsForValue().get(MSGCODE_LOGIN_MOBILE_ + mobile);
        if (StringUtils.isEmpty(cacheCode)) {
            return false;
        }
        if (!code.equals(cacheCode)) {
            return false;
        }
        return true;
    }

}
